Boxcar
A concurrent, append-only vector.
The vector provided by this crate suports concurrent get
and push
operations.
Reads are always lock-free, as are writes except when resizing is required.
Examples
Appending an element to a vector and retrieving it:
let vec = new;
vec.push;
assert_eq!;
The vector can be shared across threads with an Arc
:
use Arc;
Elements can be mutated through fine-grained locking:
use ;
Details
A vector is represented as an array of lazily allocated buckets, sizes 1, 1, 2, 4 .. 2^63
:
______________________________________
| | | | | |
| | | | | |
--------------------------------------
A buckets holds a number entries, as well as a lock used for initialization:
_____________
| | | | |
| | | | |
-------------
| UNLOCKED |
-------------
An entry holds a slot for a value along with a flag indicating whether the slot is active:
_____________________
| ACTIVE | INACTIVE |
| 42 | NULL |
---------------------
Writes acquire a unique index into the vector. The bucket holding the given entry is calculated using the leading zeros instruction. If the bucket is already initialized, the value is written to the slot, and the slot is marked as active. If the bucket has not been initialized, the thread acquires the initialization lock, allocates the bucket, and then writes the value. Note that in the general case, writes are lock-free.
Reads use the same calculation to find the entry mapped to the given index, reading the value from the slot if the flag indicates the slot is active. All reads are guaranteed to be lock-free.
Performance
Below is a benchmark in which an increasing number of elements are pushed and read from the vector by 12 threads, comparing boxcar::Vec
to RwLock<Vec>
:
The results show that boxcar::Vec
scales very well under load, performing significantly better than lock-based solutions.